/*
 * mythread.c -- implementation of user threads library
 */

#ifndef MYTHREAD_CREATE_DETACHED
#define MYTHREAD_CREATE_DETACHED 1
#endif

#ifndef MYTHREAD_CREATE_JOINABLE
#define MYTHREAD_CREATE_JOINABLE 0
#endif

#include "mythread.h"
#include <malloc.h>
#include <stdio.h>

static int mythread_init = FALSE;
static mythread_queue_t mythread_run = NULL;


//#define DEBUG

/*
 * mythread_idle - idle task just yields forever
 */
void mythread_idle(void *arg)
{
  for (;;) {
    mythread_yield();
  }
}


/*
 * mythread_self - returns the thread at the head of the ready queue
 */
mythread_t mythread_self(void)
{
	return (mythread_t)(mythread_run->item);
}

/*
 * mythread_suspend - move item from run queue to q and suspend
 */
static void mythread_suspend(mythread_queue_t *q, void *item)
{
	mythread_t current = (mythread_t) item;
	
	mythread_deq(&mythread_run, item);

	if (q != NULL) {
		mythread_enq_tail(q, item);
	}
}

/*
 * mythread_block - remove currently running thread off run q (if present),
 * if q is non-NULL and, enq this thread on q,
 * always suspend the thread,
 * add the state flags to the thread's state (via bit-wise OR | state)
 */
void mythread_block(mythread_queue_t *q, int state)
{
	mythread_t current = mythread_self();
	
	current->state |= state;
	mythread_suspend(q, (void *) current);
}

/*
 * mythread_unblock - resumes the thread at the head of q,
 * remove the state flags to the thread's state (via bit-wise AND & ~state)
 * and activate the thread
 */
void mythread_unblock(mythread_queue_t *q, int state)
{
	mythread_t current = mythread_self();
	void *item;
	
	if (q && *q) {
		item = (*q)->item;
		current = (mythread_t) item;
		current->state &= ~state;
		
		mythread_deq(q, item);
		mythread_enq_tail(&mythread_run, item);
	}
}

/*
 * mythread_exit - exit thread, awakes joiners on return
 * from thread_func and dequeue itself from run Q before dispatching run->next
 */
void mythread_exit(void *retval)
{
	mythread_t current = mythread_self();
	mythread_queue_t join;
	int joined;
	ucontext_t dummy;
	
	current->returnValue = retval;
	
	/* move joiners from join Q to run Q */
	joined = (current->joinq != NULL);
	
	if (!joined) {/* not joined --> tag thread as done: start_func = NULL */
		current->start_func = NULL;
	}
	
	while (current->joinq != NULL) {
		mythread_unblock(&current->joinq, NULL);
	}
	
	/* dequeue from run queue */
	mythread_block(NULL, NULL);
	
	/* if thread is detached, we can clean it up. */
	if (current->attr.detachstate == MYTHREAD_CREATE_DETACHED) {
	  free(current);
	}
	swapcontext(&dummy, &mythread_self()->context);
}

/*
 * mythread_wrapper - calls thread_func(thread_arg), then exits thread
 */
static mythread_wrapper(void * arg)
{
	mythread_t current = mythread_self();
	
	/* call thread_func(thread_arg) */
	void *result = current->start_func(current->arg);
	
	mythread_exit(result);
}

/*
 * mythread_newthread - allocates a TCB that th will point to afterwards,
 * creates a context that will invoke start_func(arg) inside mythread_wrapper,
 * and allocates a stack of size stacksize for this new thread.
 */
static void mythread_newthread(mythread_t *th, 
							   mythread_attr_t *attr,
							   void * (*start_func)(void *),
							   void *arg)
{
	mythread_t new;
	int stacksize;
	mythread_attr_t newattr;

	new = *th = (mythread_t) malloc(sizeof(struct mythread));
	if (new == NULL) {
	  printf("malloc error\n");
	  exit(1);
        }

	//insert attr or default attributes into new thread
	mythread_attr_init(&newattr);
	if(attr){
	  int ds, ss;
	  void *sa; 
	  mythread_attr_getdetachstate(attr,&ds);
	  mythread_attr_getstacksize(attr,&ss);
	  mythread_attr_getstackaddr(attr,&sa);

	  mythread_attr_setdetachstate(&newattr,ds);
	  mythread_attr_setstacksize(&newattr,ss);
	  mythread_attr_setstackaddr(&newattr,sa);
	  attr->stackaddr = NULL;
	}
	new->attr = newattr;
	
	getcontext(&new->context);

	//Handle stack size and stack addr in attr
	stacksize = (attr && attr->stacksize > 0) ? attr->stacksize : SIGSTKSZ;
     
	if(attr && attr->stackaddr){
	  new->context.uc_stack.ss_sp = attr->stackaddr;
	  new->context.uc_stack.ss_size = stacksize;
	}
	else{
	  new->context.uc_stack.ss_sp = malloc(stacksize);
	  new->context.uc_stack.ss_size = stacksize;
	}
	if (new->context.uc_stack.ss_sp == NULL) {
		printf("malloc error\n");
		exit(1);
	}

	makecontext(&new->context, (void *) mythread_wrapper, NULL);
	new->start_func = start_func;
	new->arg = arg;
}

/*
 * mythread_create - prepares context of new_thread_ID as start_func(arg),
 * attr is used to determine stack size (optionally).
 * If mythread_init is FALSE, the ready and the run Q are initialized.
 * Threads are activated (run) in FIFO.
 */
int mythread_create(mythread_t *new_thread_ID,
					mythread_attr_t *attr,
					void * (*start_func)(void *),
					void *arg)
{
	mythread_queue_t newq;
	mythread_t mainth;
	mythread_t idleth;
	int i, size;
	
	if (!mythread_init) { /* initialize */
	  mythread_init = TRUE;
		
		/* main thread */
		mainth = (mythread_t) malloc(sizeof(struct mythread));
		if (mainth == NULL) {
			printf("malloc error\n");
			exit(1);
		}
		
		
		mythread_q_init(&mythread_run, mainth);
		mainth->start_func = NULL;
		mainth->arg = NULL;
		mainth->state = NULL;

		/* idle thread */
		mythread_newthread(&idleth, NULL, mythread_idle, NULL);
		mythread_enq_tail(&mythread_run, (void *) idleth);
	}

	/* new thread */
	mythread_newthread(new_thread_ID, attr, start_func, arg);
	(*new_thread_ID)->state = NULL;
	mythread_enq_tail(&mythread_run, (void *) *new_thread_ID);
       
	return 0;
}

/*
 * mythread_yield - switch from running thread to the next ready one
 */
int mythread_yield(void)
{
	mythread_t current = mythread_self();
	
	if (mythread_run != NULL) {
		mythread_suspend(&mythread_run, (void *) current);
		swapcontext(&current->context, &mythread_self()->context);
	}
	
	return 0;
}

/*
 * mythread_join - suspend calling thread if target_thread has not finished,
 * enqueue on the join Q of the target thread, then dispatch ready thread;
 * once target_thread finishes, it activates the calling thread / marks it
 * as ready.
 */
int mythread_join(mythread_t target_thread, void **status)
{
	mythread_t current = mythread_self();
	
	if(target_thread->attr.detachstate == MYTHREAD_CREATE_DETACHED)
	{
		return -1;		//error
	}
	
	
	if (target_thread->start_func != NULL) {
		/* block the calling thread and switch to the next ready thread */
		mythread_block(&target_thread->joinq, NULL);
		swapcontext(&current->context, &mythread_self()->context);
	}
	
	if (status) {
	  *status = target_thread->returnValue;
	}
	
	return 0;
}

/*
 * mythread_attr_setdetachstate - set the detachstate attribute in *tattr to the state
 * specified by detachstate param (detached or joinable). Default value is joinable. Returns 0 on succes,
 * -1 otherwise.
 */
int mythread_attr_setdetachstate(mythread_attr_t *tattr, int detachstate)
{
	if (tattr && (detachstate == MYTHREAD_CREATE_JOINABLE || detachstate == MYTHREAD_CREATE_DETACHED)) {
		tattr->detachstate = detachstate;
		return 0;
	}
	else
		return -1;
}

/*
 * mythread_attr_getdetachstate - return (in *detachstate)  the thread create state, 
 * that is the detachstate attribute of *tattr, which is either detached or joinable.
 */
int mythread_attr_getdetachstate(const mythread_attr_t *tattr, int *detachstate)
{
	if (!detachstate || !tattr)
		return -1;
	
	*detachstate = tattr->detachstate;
	return 0;
}

/*
 * mythread_attr_init - initialize attribute object, *tattr, to default 
 * attribute values.
 */
int mythread_attr_init(mythread_attr_t *tattr)
{
	if (!tattr) return -1;
	tattr->detachstate = MYTHREAD_CREATE_JOINABLE;
	tattr->stacksize = 0;
	tattr->stackaddr = NULL;
	return 0;
}

/*
 * mythread_attr_getstackaddr - return the base address for the thread's stack,
 * that is, the *stackaddr attribute of *tattr.
 */
int mythread_attr_getstackaddr(mythread_attr_t *tattr, void **stackaddr)
{
	if(tattr && stackaddr)
	{
		*stackaddr = tattr->stackaddr;
		return 0;
	}
	else
		return -1;
}

/*
 * mythread_attr_setstackaddr - set the base address of the stack of the thread that
 * is to be created.
 */
int mythread_attr_setstackaddr(mythread_attr_t *tattr, void *stackaddr)
{
	if(tattr)
	{
	  tattr->stackaddr = stackaddr;
		return 0;
	}
	else
		return -1;
}

/*
 * mythread_attr_getstacksize - return (in *size) the thread creation stack size defined
 * in *tattr. 
 */
int mythread_attr_getstacksize(mythread_attr_t *tattr, int *stacksize)
{
	if (tattr && stacksize)
	{
		*stacksize = tattr->stacksize;
		return 0;
	}
	else
		return -1;
}

/*
 * mythread_attr_setstacksize - define the thread creation stack size attribute in 
 * tattr. stacksize should not be less than the system-defined minimum stack size.
 */
int mythread_attr_setstacksize(mythread_attr_t *tattr, int stacksize)
{
  	if(tattr)
	{
		tattr->stacksize = stacksize;
		return 0;
	 }
	 else
	    return -1;
}

/*
 * mythread_attr_destroy - remove resources allocated for *tattr during 
 * initialization.
 */
int mythread_attr_destroy(mythread_attr_t *tattr)
{
    if(!tattr)
	 	return -1;
    free(tattr->stackaddr);
    tattr = NULL;
	
	return 0;
}

/*
 * mythread_detach - make target_thread unable to be joined with mythread_join(). 
 * target_thread will be freed immediately upon termination rather than waiting to 
 * be joined by another thread.
 */
int mythread_detach(mythread_t target_thread)
{
	target_thread->attr.detachstate = MYTHREAD_CREATE_DETACHED;
	
	/* Unblock any threads joined with target_thread prior to it being detached */
		
	while (target_thread->joinq != NULL) {
		mythread_unblock(&target_thread->joinq, NULL);
	}
	return 0;
}

